<script>
    const TYPE = {
        object: '[object Object]',
        array: '[object Array]',
        number: '[object Number]',
        string: '[object String]',
        boolean: '[object Boolean]',
        null: '[object Null]',
        undefined: '[object Undefined]',
        symbol: '[object Symbol]',
        function: '[object Function]',
    };
    const BRACKET = {
        '[object Object]': ['{', '}'],
        '[object Array]': ['[', ']']
    };
    const KEY = {
        '[object Object]': true,
        '[object Array]': false
    };
    function getType(o) {
        return Object.prototype.toString.call(o);
    }
    function collapseHelp(v, d) {
        if(getType(v) === TYPE.number) {
            return d > v;
        }
        return !!v;
    }
    function genDom(o, h, vm, deep) {
        let type = getType(o);
        let ar = [], isKey = false;
        if(type === TYPE.object || type === TYPE.array) {
            isKey = KEY[type];
            for(let key in o) {
                type = getType(o[key]);
                if(type === TYPE.object || type === TYPE.array) {
                    ar.push(
                        h('div', {class: {'is-fold': collapseHelp(vm.collapsed, deep), 'cc-json-inner': true}}, [
                            h('div', {class: 'cc-json-inner', on: {mouseover: vm.handleHover, mouseout: vm.handleOut}}, [
                                h('span', {class: 'cc-json-plus', on: {click: vm.handleExpand}}),
                                isKey ? h('span', {class: 'cc-json-key'}, key) : '',
                                isKey ? h('span', {class: 'cc-json-colon'}, ':') : '',
                                h('span', {class: 'cc-json-bracket'}, BRACKET[type][0]),
                                vm.showDeep ? h('i', {class: 'cc-json-deep'}, deep) : '',
                                vm.showCopy ? h('span', {class: 'cc-json-copy', on: {click: vm.handleCopy(o[key])}}) : '',
                            ]),
                            genDom(o[key], h, vm, deep + 1),
                            h('div', {class: 'cc-json-inner'}, [
                                h('span', {class: 'cc-json-ellipsis', on: {click: vm.handleExpand}}, '...'),
                                h('span', {class: 'cc-json-bracket'}, BRACKET[type][1])
                            ])
                        ])
                    );
                } else {
                    ar.push(
                        h('div', {class: 'cc-json-inner', on: {mouseover: vm.handleHover, mouseout: vm.handleOut}}, [
                            isKey ? h('span', {class: 'cc-json-key'}, key) : '',
                            isKey ? h('span', {class: 'cc-json-colon'}, ':') : '',
                            genDom(o[key], h, vm, deep + 1),
                            vm.showCopy ? h('span', {class: 'cc-json-copy', on: {click: vm.handleCopy(o[key])}}) : ''
                        ])
                    );
                }
            }
            return h('div', {class: 'cc-json-content'}, ar);
        }
        if(type === TYPE.number) return h('span', {class: 'cc-json-value-number'}, o);
        if(type === TYPE.boolean) return h('span', {class: 'cc-json-value-number'}, o ? 'true': 'false');
        if(type === TYPE.null) return h('span', {class: 'cc-json-value-string'}, 'null');
        if(type === TYPE.undefined) return h('span', {class: 'cc-json-value-string'}, 'undefined');
        if(type === TYPE.function) return h('span', {class: 'cc-json-value-string'}, '');
        return h('span', {class: 'cc-json-value-string'}, o);
    }

    function toString(v) {
        let type = getType(v);
        if(type === TYPE.object || type === TYPE.array) return JSON.stringify(v);
        if(type === TYPE.null) return 'null';
        if(type === TYPE.undefined) return 'undefined';
        if(type === TYPE.symbol) return v.toString();
        if(type === TYPE.function) return '';
        return v;
    }

    export default {
        name: "ccJsonView",
        props: {
            data: {
                type: Object,
                default() {
                    return {}
                }
            },
            options: {
                type: Object,
                default() {
                    return {}
                }
            }
        },
        created() {
            this.showDeep = this.options.showDeep;
            this.showCopy = this.options.showCopy;
            this.collapsed = this.options.collapsed; //accept integer
        },
        methods: {
            handleExpand(e) {
                let target = e.target.parentNode.parentNode;
                target.classList.contains('is-fold') ?
                    target.classList.remove('is-fold') : target.classList.add('is-fold');
            },
            handleHover(e) {
                let target = e.currentTarget;
                target.classList.add('is-hover');
            },
            handleOut(e) {
                let target = e.currentTarget;
                target.classList.remove('is-hover');
            },
            handleCopy(txt) {
                return function(e) {
                    var dom = document.createElement('fake');
                    var selection = window.getSelection();
                    var range = document.createRange();
                    var err = 0;
                    var target = e.currentTarget;
                    dom.innerText = toString(txt);
                    document.body.appendChild(dom);
                    selection.removeAllRanges();
                    range.selectNode(dom);
                    selection.addRange(range);
                    try {
                        document.execCommand('copy');
                    } catch(e) {
                        err = 1;
                    }
                    dom.remove();
                    if(!err) {
                        target.classList.add('cc-json-copy-success');
                        setTimeout(() => {
                            target.classList.remove('cc-json-copy-success');
                        }, 1000);
                    }
                }
            }
        },
        render(h) {
            let vm = this;
            return h('div', {class: 'cc-json'}, [
                    h('div', {class: {'is-fold': collapseHelp(vm.collapsed, 1), 'cc-json-inner': true}}, [
                        h('div', {class: 'cc-json-inner', on: {mouseover: vm.handleHover, mouseout: vm.handleOut}}, [
                            h('span', {class: 'cc-json-plus', on: {click: vm.handleExpand}}),
                            h('span', {class: 'cc-json-bracket'}, '{'),
                            vm.showDeep ? h('i', {class: 'cc-json-deep'}, 0) : '',
                            vm.showCopy ? h('span', {class: 'cc-json-copy', on: {click: vm.handleCopy(this.data)}}) : '',
                        ]),
                        genDom(this.data, h, vm, 1),
                        h('div', {class: 'cc-json-inner'}, [
                            h('span', {class: 'cc-json-ellipsis', on: {click: vm.handleExpand}}, '...'),
                            h('span', {class: 'cc-json-bracket'}, '}')
                        ])
                    ])
                ])
        },
        data() {
            return {
                showDeep: false,
                collapsed: false,
                showCopy: false
            }
        }
    }
</script>

<style lang="scss">
    .cc-json {
        font-size: 13px;
        text-align: left;
        span {
            vertical-align: middle;
            font-family: monospace;
        }
        .cc-json-inner {
            line-height: 20px;
        }
        .cc-json-ellipsis {
            font-weight: bold;
            cursor: pointer;
            display: none;
            color: #000a25;
        }
        .cc-json-value-string {
            color: #008216;
        }
        .cc-json-value-number {
            color: #d20000;
        }
        .cc-json-copy {
            background-image: url();
            background-size: 100% 100%;
            background-attachment: scroll;
            background-position: center center;
            width: 17px;
            height: 17px;
            margin-left: 5px;
            display: none;
            &.cc-json-copy-success {
                background-image: url();
            }
        }
        .cc-json-deep {
            font-weight: normal;
            color: #d0d0d0;
            margin-left: 5px;
        }
        .cc-json-inner {
            &.is-fold {
                > .cc-json-content {
                   display: none;
                }
                > .cc-json-inner {
                    display: inline;
                    > .cc-json-ellipsis {
                        display: inline;
                    }
                }
                .cc-json-> .cc-json-ellipsis {
                    display: inline;
                }
                > .cc-json-deep {
                    display: none;
                }
                > .cc-json-plus {
                    transform: rotate(0);
                }
            }
            &.is-hover {
                background: #f1f1f1;
                .cc-json-copy {
                    display: inline-block;
                }
            }
        }
        .cc-json-plus {
            cursor: pointer;
            border-top: 3px solid transparent;
            border-bottom: 3px solid transparent;
            border-left: 5px solid #000a25;
            margin-right: 7px;
            width: 0;
            height: 0;
            display: inline-block;
            vertical-align: middle;
            transform: rotate(90deg);
        }
        .cc-json-bracket {
            font-weight: bold;
            color: #000a25;
            margin: 0 3px;
        }
        .cc-json-content {
           padding-left: 16px;
            transition-duration: .3s;
            margin-left: 2px;
            border-left: 1px solid #dcdcdc;
        }
        .cc-json-key {
            color: #2c4b71;
        }
        .cc-json-colon {
            margin: 0 5px;
            font-weight: bold;
            color: #2c4b71;
        }
    }
</style>